package gek.uni.diplom.integrate.wizard.actions;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl;
import org.eclipse.m2m.atl.drivers.emf4atl.ASMEMFModel;
import org.eclipse.m2m.atl.drivers.emf4atl.EMFModelLoader;
import org.eclipse.m2m.atl.engine.vm.AtlLauncher;
import org.eclipse.m2m.atl.engine.vm.AtlModelHandler;
import org.eclipse.m2m.atl.engine.vm.ModelLoader;
import org.eclipse.m2m.atl.engine.vm.nativelib.ASMModel;

import RootDesc.RootDescPackage;

public class ATLTransformation {
  public static final String USAGE =
      "Usage: <this program> " + "--trans <transformation url> "
          + "[--in <id>=<model> <id>=<metamodel> <MDR|EMF>] "
          + "[--out <id>=<model> <id>=<metamodel> <MDR|EMF>] "
          + "[--lib <id>=<library url>] "
          + "[--superimpose <transformation url>] " + "--next --trans ...";

  private URL trans = null;
  private Map in = new HashMap();
  private HashMap out = new HashMap();
  private HashMap libs = new HashMap();
  private HashMap paths = new HashMap();
  private HashMap modelCache = new HashMap();
  private HashMap loaders = new HashMap();
  private ArrayList superimpose = new ArrayList();

  private static URI cwd =
      URI.createURI("file:" + new File(".").getAbsolutePath());

  private int argPos = 0;

  public void main() throws Exception {
    URL url = new URL(
    // "platform:/plugin/gek.uni.diplom.tfg.atl.transform/StateChart2TFG_Simple/StateChart2XSD.atl)");
        "file:D:/StateChart2XSD.atl");

    addInputModel(
        // "IN=platform:/plugin/gek.uni.diplom.tfg.uml.diagrams/Models/Compound/CompositeStateMachine.uml",
        "IN=file:D:/CompositeStateMachine.uml",
        "UML=platform:/plugin/org.eclipse.uml2.uml/model/UML.ecore", "EMF");
    // "UML=file:D:/UML.ecore","EMF");

    // loadMyModel();
    addOutputModel(
        // "OUT=platform:/plugin/gek.uni.diplom.tfg.atl.transform/out.tstDsc",
        "OUT=file:D:/out.tstDsc",
        "TFG=platform:/plugin/gek.uni.diplom.tfg.metamodel/TestDesc1.ecore",
        "EMF");
    // "TFG=file:D:/TestDesc1.ecore", "EMF");
  }

  private void loadMyModel() {
    ResourceSet resourceSet = new ResourceSetImpl();
    resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
        Resource.Factory.Registry.DEFAULT_EXTENSION,
        new EcoreResourceFactoryImpl());
    resourceSet.getPackageRegistry().put(RootDescPackage.eNAME,
        RootDescPackage.eINSTANCE);
    String fileName = "D:\\example_tfg.xml";
    File file = new File(fileName);
    URI uri = URI.createFileURI(file.getAbsolutePath());
    Resource resource = resourceSet.getResource(uri, true);
  }

  public static AtlModelHandler getDefaultHandler(String repository) {
    AtlModelHandler amh = null;
    try {
      amh = AtlModelHandler.getDefault(repository);
    } catch (Throwable e) {}
    return amh;
  }

  public ModelLoader getModelLoader(AtlModelHandler amh) {
    ModelLoader ml = (ModelLoader) loaders.get(amh);
    if (ml == null) {
      ml = amh.createModelLoader();
      loaders.put(amh, ml);
    }
    return ml;
  }

  /**
   * Loads a model via URI if possible, via InputStream otherwise.
   * 
   * @param ml
   *          The model loader to use.
   * @param modelId
   *          The ATL model ID.
   * @param metaModel
   *          The ATL meta-model.
   * @param uri
   *          The URI of the model to load.
   * @return The loaded ATL model.
   * @throws IOException
   */
  public static ASMModel loadModel(ModelLoader ml, String modelId,
      ASMModel metaModel, String uri) throws IOException {
    ASMModel model = null;
    if (ml instanceof EMFModelLoader) {
      URI absURI = URI.createURI(uri);// .resolve(cwd, true);
      ((EMFModelLoader) ml).getResourceSet().getResourceFactoryRegistry()
          .getExtensionToFactoryMap().put(
              Resource.Factory.Registry.DEFAULT_EXTENSION,
              new EcoreResourceFactoryImpl());

      if (uri
//          .equals("platform:/plugin/gek.uni.diplom.tfg.metamodel/TestDesc1.ecore")) {
          .equals("file:D:/TestDesc1.ecore")) {
        ((EMFModelLoader) ml).getResourceSet().getPackageRegistry().put(
            RootDescPackage.eNAME, RootDescPackage.eINSTANCE);
        ((EMFModelLoader) ml).getResourceSet().getPackageRegistry().put(
            "platform:/plugin/gek.uni.diplom.tfg.metamodel/TestDesc1.ecore",
            RootDescPackage.eINSTANCE);
      }

      model = ((EMFModelLoader) ml).loadModel(modelId, metaModel, absURI);
    } else {
      model = ml.loadModel(modelId, metaModel, new FileInputStream(uri));
    }
    return model;
  }

  /**
   * Logs the given strings, separated by spaces.
   * 
   * @param strings
   */
  private void logStrings(String[] strings) {
    StringBuffer output = new StringBuffer();
    for (int i = 0; i < strings.length; i++) {
      if (i > 0) {
        output.append(' ');
      }
      output.append(strings[i]);
    }
    System.out.println(output.toString());
  }

  /**
   * Parses the command line arguments
   * 
   * @param args
   * @return number of arguments left (e.g. if "--next" is used) or -1 if error
   */
  public int parseArgs(String[] args) {
    if (args.length <= argPos + 1) {
      System.out.println(USAGE);
      return -1;
    }
    try {
      for (int i = argPos; i < args.length; i++) {
        if (args[i].equals("--trans")) {
          i++;
          logStrings(new String[] { args[i - 1], args[i] });
          trans = new URL(args[i]);
        } else if (args[i].equals("--in")) {
          i++;
          i++;
          i++;
          logStrings(new String[] { args[i - 3], args[i - 2], args[i - 1],
              args[i] });
          addInputModel(args[i - 2], args[i - 1], args[i]);
        } else if (args[i].equals("--out")) {
          i++;
          i++;
          i++;
          logStrings(new String[] { args[i - 3], args[i - 2], args[i - 1],
              args[i] });
          addOutputModel(args[i - 2], args[i - 1], args[i]);
        } else if (args[i].equals("--lib")) {
          i++;
          logStrings(new String[] { args[i - 1], args[i] });
          addLib(args[i]);
        } else if (args[i].equals("--superimpose")) {
          i++;
          logStrings(new String[] { args[i - 1], args[i] });
          superimpose.add(new URL(args[i]));
        } else if (args[i].equals("--next")) {
          i++;
          argPos = i;
          logStrings(new String[] { args[i - 1] });
          return args.length - argPos;
        } else {
          System.out.print(USAGE);
          return -1;
        }
      }
    } catch (Exception e) {
      System.err.print(e.toString());
      e.printStackTrace();
      System.out.print(USAGE);
      return -1;
    }
    return 0;
  }

  /**
   * Parses --in arguments and adds them to the internal list
   * 
   * @param model
   * @param metamodel
   * @param repository
   * @throws Exception
   */
  public void addInputModel(String model, String metamodel, String repository)
      throws Exception {
    StringTokenizer mdl = new StringTokenizer(model, "=");
    String modelid = mdl.nextToken();
    String modelpath = mdl.nextToken();
    StringTokenizer metamdl = new StringTokenizer(metamodel, "=");
    String metaid = metamdl.nextToken();
    String metapath = metamdl.nextToken();

    AtlModelHandler amh = getDefaultHandler(repository);
    ModelLoader ml = getModelLoader(amh);
    ASMModel metaModel;
    ASMModel inputModel;
    if (metaid.equals("MOF")) {
      System.out.println("Input metamodel is MOF - using built-in metamodel");
      metaModel = ml.getMOF();
    } else {
      metaModel = (ASMModel) in.get(metaid);
      if (metaModel == null || !metapath.equals(paths.get(metaid))) {
        System.out.println("Input metamodel " + metaid + " @ " + amh
            + " not yet loaded - loading from " + metapath);
        metaModel = loadModel(ml, metaid, ml.getMOF(), metapath);
        in.put(metaid, metaModel);
      }
    }
    System.out.println("Using input metamodel " + metaModel);
    inputModel = (ASMModel) modelCache.get(modelid);
    if (inputModel == null || !modelpath.equals(paths.get(modelid))
        || !metaid.equals(inputModel.getMetamodel().getName())) {
      System.out.println("Loading input model " + modelid + " from "
          + modelpath);
      inputModel = loadModel(ml, modelid, metaModel, modelpath);
      if (inputModel instanceof ASMEMFModel)
        ((ASMEMFModel) inputModel).setCheckSameModel(false);
      modelCache.put(modelid, inputModel);
    }
    System.out.println("Using input model " + inputModel);
    in.put(modelid, inputModel);
    paths.put(modelid, modelpath);
    paths.put(metaid, metapath);
  }

  /**
   * Parses --out arguments and adds them to the internal list
   * 
   * @param model
   * @param metamodel
   * @param repository
   * @throws Exception
   */
  public void addOutputModel(String model, String metamodel, String repository)
      throws Exception {
    StringTokenizer mdl = new StringTokenizer(model, "=");
    String modelid = mdl.nextToken();
    String modelpath = mdl.nextToken();
    StringTokenizer metamdl = new StringTokenizer(metamodel, "=");
    String metaid = metamdl.nextToken();
    String metapath = metamdl.nextToken();

    AtlModelHandler amh = getDefaultHandler(repository);
    ModelLoader ml = getModelLoader(amh);
    ASMModel metaModel;
    ASMModel outputModel;
    if (metaid.equals("MOF")) {
      System.out.println("Output metamodel is MOF - using built-in metamodel");
      metaModel = ml.getMOF();
    } else {
      metaModel = (ASMModel) in.get(metaid);
      if (metaModel == null || !metapath.equals(paths.get(metaid))) {
        System.out.println("Loading output metamodel " + metaid + " @ " + amh
            + " from " + metapath);
        metaModel = loadModel(ml, metaid, ml.getMOF(), metapath);
        in.put(metaid, metaModel);
      }
    }
    System.out.println("Using output metamodel " + metaModel);
    System.out.println("Creating new model " + modelid + " for output");
    outputModel = ml.newModel(modelid, modelpath, metaModel);
    if (outputModel instanceof ASMEMFModel)
      ((ASMEMFModel) outputModel).setCheckSameModel(false);
    out.put(modelid, outputModel);
    paths.put(modelid, modelpath);
    paths.put(metaid, metapath);
  }

  /**
   * Parses --lib arguments and adds them to the internal list
   * 
   * @param lib
   * @throws Exception
   */
  public void addLib(String lib) throws Exception {
    StringTokenizer l = new StringTokenizer(lib, "=");
    String libid = l.nextToken();
    URL liburl = new URL(l.nextToken());
    libs.put(libid, liburl);
  }

  /**
   * Executes the ATL engine
   */
  public void run() {
    try {
      System.out.println("Starting model transformation " + trans);
      Map models = new HashMap();
      // add input models
      for (Iterator i = in.keySet().iterator(); i.hasNext();) {
        String mName = (String) i.next();
        models.put(mName, in.get(mName));
      }
      // add output models
      for (Iterator i = out.keySet().iterator(); i.hasNext();) {
        String mName = (String) i.next();
        models.put(mName, out.get(mName));
      }
      Map params = Collections.EMPTY_MAP;
      Map options = Collections.EMPTY_MAP;
      AtlLauncher myLauncher = AtlLauncher.getDefault();
      myLauncher.launch(trans, libs, models, params, superimpose, options);
      System.out.println("Model transformation done");
      // save output models
      for (Iterator i = out.keySet().iterator(); i.hasNext();) {
        String mName = (String) i.next();
        ASMModel currentOutModel = (ASMModel) out.get(mName);
        currentOutModel.getModelLoader().save(currentOutModel,
            (String) paths.get(mName));
        System.out.println("Wrote " + (String) paths.get(mName));
        modelCache.put(mName, currentOutModel);
        i.remove();
      }
    } catch (Exception e) {
      System.err.print(e.toString());
      e.printStackTrace();
      System.out.println(USAGE);
      System.exit(1);
    }
  }
}
